<script setup lang="ts" name="VerifySlide">
/**
 * VerifySlide
 * @description 滑块
 * */
import { encodeAES } from '@/utils/aesRsa'
import { AuthApi } from '@/api/auth'
import { isSuccess } from '@/utils/axios/helper'
import { storeToRefs } from 'pinia'
import { useAppStoreWithOut } from '@/store/modules/app'

const appStore = useAppStoreWithOut()
const { systemConfig } = storeToRefs(appStore)
const { t } = useI18n()
const props = defineProps({
  captchaType: {
    type: String,
    default: ''
  },
  mode: {
    type: String,
    default: 'dialog' // dialog (弹出式) | hover（触发式） | embedded（嵌入式）
  },
  size: {
    type: String,
    default: '' // '' (系统默认) | default（默认大小）| large（大） | small（小）
  }
})
const emit = defineEmits(['success', 'error'])
const { captchaType, mode, size } = toRefs(props)
const secretKey = ref('') //后端返回的ase加密秘钥
const isPass = ref<boolean | undefined>(undefined) //是否通过的标识
const slideImage = ref('') //验证码背景图片
const slideImageBlock = ref('') //验证滑块的背景图片
const token = ref('') //后端返回的唯一token值
const startMoveTime = ref(0) //移动开始的时间
const endMoveTime = ref(0) //移动结束的时间
const text = ref('')
const VerifySlideControlRef: any = ref()
const VerifyImagePanelRef: any = ref()
const VerifySlideImageRef: any = ref()
const verifySlideButtonRef: any = ref()
const VerifyImageWrapRef: any = ref()
const slideImageLeft = ref(0)
const slideButtonLeft = ref(0)
const slideIndicatorWidth = ref(32)
const isMouseDown = ref(false)
const loading = ref(true)
const startLeft = ref(0)
const mouseDownType = ref('')
const sizePx = ref('32px')
const newSize = ref('default')
const isVisible = ref(false)

const initSize = () => {
  if (size.value) {
    newSize.value = size.value
  } else {
    newSize.value = systemConfig.value.size
  }
  const sizeText = newSize.value === 'default' ? '' : '-' + newSize.value
  const htmlElement = document.querySelector('html') as HTMLHtmlElement | null
  if (htmlElement) {
    const root = getComputedStyle(htmlElement)
    sizePx.value = root.getPropertyValue('--el-component-size' + sizeText)
    slideIndicatorWidth.value = parseInt(sizePx.value)
  }
  console.log(newSize, sizePx.value, slideIndicatorWidth.value)
}

onMounted(() => {
  initSize()
  init()
  // 禁止拖拽
  getPicture()
})

//鼠标按下
const handleSlideButtonMouseDown = (e) => {
  if (isPass.value !== true) {
    isMouseDown.value = true
    mouseDownType.value = 'control'
    e = e || window.event
    if (!e.touches) {
      //兼容PC端
      var x = e.clientX
    } else {
      //兼容移动端
      var x = e.touches[0].pageX
    }
    startLeft.value = Math.floor(x - VerifySlideControlRef.value.getBoundingClientRect().left)
    startMoveTime.value = +new Date() //开始滑动的时间
    window.addEventListener('touchmove', handleSlideButtonMouseMove)
    window.addEventListener('mousemove', handleSlideButtonMouseMove)
    window.addEventListener('touchmove', handleSlideButtonMouseMove)
    window.addEventListener('touchend', handleMouseUp)
    window.addEventListener('mouseup', handleMouseUp)
  }
}
//鼠标移动
const handleSlideButtonMouseMove = (e) => {
  if (isMouseDown.value) {
    e = e || window.event
    if (!e.touches) {
      //兼容PC端
      var x = e.clientX
    } else {
      //兼容移动端
      var x = e.touches[0].pageX
    }
    const moveLeft = x - VerifySlideControlRef.value.getBoundingClientRect().left //小方块相对于父元素的left值
    const maxLeft = VerifySlideControlRef.value.offsetWidth - (verifySlideButtonRef.value.offsetWidth - startLeft.value) - 2
    if (moveLeft <= startLeft.value) {
      slideImageLeft.value = 0
      slideButtonLeft.value = 0
      slideIndicatorWidth.value = parseInt(sizePx.value)
    } else if (moveLeft >= maxLeft) {
      slideImageLeft.value = VerifyImagePanelRef.value.offsetWidth - VerifySlideImageRef.value.offsetWidth
      slideButtonLeft.value = VerifySlideControlRef.value.offsetWidth - verifySlideButtonRef.value.offsetWidth - 2
      slideIndicatorWidth.value = VerifySlideControlRef.value.offsetWidth
    } else {
      const percent = (moveLeft - startLeft.value) / (VerifySlideControlRef.value.offsetWidth - verifySlideButtonRef.value.offsetWidth - 2)
      slideImageLeft.value = Math.floor((VerifyImagePanelRef.value.offsetWidth - VerifySlideImageRef.value.offsetWidth) * percent)
      slideButtonLeft.value = moveLeft - startLeft.value
      slideIndicatorWidth.value = moveLeft - startLeft.value + parseInt(sizePx.value)
    }
  }
}

//鼠标松开
const handleMouseUp = () => {
  isMouseDown.value = false
  endMoveTime.value = +new Date()
  if (mouseDownType.value === 'control') {
    window.removeEventListener('touchmove', handleSlideButtonMouseMove)
    window.removeEventListener('mousemove', handleSlideButtonMouseMove)
  } else {
    window.removeEventListener('touchmove', handleSlideImageMouseMove)
    window.removeEventListener('mousemove', handleSlideImageMouseMove)
  }
  //判断是否重合
  let data = {
    captchaType: captchaType.value,
    pointJson: secretKey.value
      ? encodeAES(JSON.stringify({ x: slideImageLeft.value, y: 0 }), secretKey.value)
      : JSON.stringify({ x: slideImageLeft.value, y: 0 }),
    token: token.value
  }
  AuthApi.Captcha.check(data).then((res: any) => {
    if (isSuccess(res)) {
      isPass.value = true
      text.value = `${((endMoveTime.value - startMoveTime.value) / 1000).toFixed(2)}s ${t('captcha', 'success')}`
      var captchaVerification = secretKey.value
        ? encodeAES(
          token.value + '---' + JSON.stringify({ x: slideImageLeft.value, y: 0 }),
          secretKey.value
        )
        : token.value + '---' + JSON.stringify({ x: slideImageLeft.value, y: 0 })
      emit('success', { captchaVerification })
      setTimeout(() => {
        window.removeEventListener('touchend', handleMouseUp)
        window.removeEventListener('mouseup', handleMouseUp)
      }, 600);
    } else {
      isPass.value = false
      emit('error')
      text.value = t('captcha', 'fail')
      setTimeout(function () {
        refresh()
      }, 500)
      setTimeout(() => {
        isPass.value = undefined
      }, 600)
    }
  })
}

//鼠标按下
const handleSlideImageMouseDown = (e) => {
  if (isPass.value !== true) {
    isMouseDown.value = true
    mouseDownType.value = 'image'
    e = e || window.event
    if (!e.touches) {
      //兼容PC端
      var x = e.clientX
    } else {
      //兼容移动端
      var x = e.touches[0].pageX
    }
    startLeft.value = Math.floor(x - VerifyImagePanelRef.value.getBoundingClientRect().left)
    startMoveTime.value = +new Date() //开始滑动的时间
    window.addEventListener('touchmove', handleSlideImageMouseMove)
    window.addEventListener('mousemove', handleSlideImageMouseMove)
    window.addEventListener('touchend', handleMouseUp)
    window.addEventListener('mouseup', handleMouseUp)
  }
}
//鼠标移动
const handleSlideImageMouseMove = (e) => {
  if (isMouseDown.value) {
    e = e || window.event
    if (!e.touches) {
      //兼容PC端
      var x = e.clientX
    } else {
      //兼容移动端
      var x = e.touches[0].pageX
    }
    const moveLeft = x - VerifyImagePanelRef.value.getBoundingClientRect().left //小方块相对于父元素的left值
    const maxLeft = VerifyImagePanelRef.value.offsetWidth - (VerifySlideImageRef.value.offsetWidth - startLeft.value)
    if (moveLeft <= startLeft.value) {
      slideImageLeft.value = 0
      slideButtonLeft.value = 0
      slideIndicatorWidth.value = parseInt(sizePx.value)
    } else if (moveLeft >= maxLeft) {
      slideImageLeft.value = VerifyImagePanelRef.value.offsetWidth - VerifySlideImageRef.value.offsetWidth
      slideButtonLeft.value = VerifySlideControlRef.value.offsetWidth - verifySlideButtonRef.value.offsetWidth - 2
      slideIndicatorWidth.value = VerifySlideControlRef.value.offsetWidth
    } else {
      const percent = (moveLeft - startLeft.value) / (VerifyImagePanelRef.value.offsetWidth - VerifySlideImageRef.value.offsetWidth)
      slideImageLeft.value = moveLeft - startLeft.value
      slideButtonLeft.value = Math.floor((VerifySlideControlRef.value.offsetWidth - verifySlideButtonRef.value.offsetWidth - 2) * percent)
      slideIndicatorWidth.value = Math.floor((VerifySlideControlRef.value.offsetWidth - verifySlideButtonRef.value.offsetWidth - 2) * percent) + parseInt(sizePx.value)
    }
  }
}

const init = () => {
  startMoveTime.value = 0
  endMoveTime.value = 0
  text.value = ''
  slideImageLeft.value = 0
  slideButtonLeft.value = 0
  slideIndicatorWidth.value = parseInt(sizePx.value)
  isMouseDown.value = false
  loading.value = false
  startLeft.value = 0
  window.removeEventListener('touchend', handleMouseUp)
  window.removeEventListener('mouseup', handleMouseUp)
}

const handleMouseEnter = () => {
  if (mode.value === 'hover') {
    isVisible.value = true
  }
}

const handleMouseLeave = () => {
  if (mode.value === 'hover') {
    window.addEventListener('touchmove', handleMouseMove)
    window.addEventListener('mousemove', handleMouseMove)
  }
}

const handleMouseMove = (e) => {
  if (isMouseDown.value) {
    isVisible.value = true
  } else {
    e = e || window.event
    if (!e.touches) {
      //兼容PC端
      var x = e.clientX
      var y = e.clientY
    } else {
      //兼容移动端
      var x = e.touches[0].pageX
      var y = e.touches[0].pageY
    }
    const otherDomRect = VerifyImageWrapRef.value.getBoundingClientRect()
    const enterDomRect = VerifySlideControlRef.value.getBoundingClientRect()
    if (x >= enterDomRect.left && x <= enterDomRect.right && y >= enterDomRect.top && y <= enterDomRect.bottom ) {
      isVisible.value = true
    } else {
      if (!(x >= otherDomRect.left && x <= otherDomRect.right && y >= otherDomRect.top && y <= otherDomRect.bottom)) {
        isVisible.value = false
        window.removeEventListener('touchend', handleMouseMove)
        window.removeEventListener('mouseup', handleMouseMove)
      }
    }
  }
}


const refresh = async () => {
  init()
  await getPicture()
}

defineExpose({ refresh })

// 请求背景图片和验证图片
const getPicture = async () => {
  let data = {
    captchaType: captchaType.value
  }
  loading.value = true
  try {
    const res: any = await AuthApi.Captcha.code(data)
    if (isSuccess(res)) {
      slideImage.value = res.data.originalImageBase64
      slideImageBlock.value = res.data.jigsawImageBase64
      token.value = res.data.token
      secretKey.value = res.data.secretKey
    } else {
      text.value = res.msg
    }
  } finally {
    loading.value = false
  }
}

watch(
  () => systemConfig.value.size,
  () => {
    initSize()
  },
  {
    immediate: true
  }
)
</script>

<template>
  <div class="verify-box" :class="`verify-box-${newSize}`">
    <div ref="VerifyImageWrapRef" class="verify-image-wrap" :class="[mode === 'hover' ? 'is-hover-mode' : '', isVisible ? 'is-visible': '']">
      <div ref="VerifyImagePanelRef" class="verify-image-panel">
        <img class="verify-bg-image" :src="slideImage" />
        <img ref="VerifySlideImageRef" class="verify-slide-image" :src="slideImageBlock" :style="{ transform: 'translateX(' + slideImageLeft + 'px)' }" @mousedown="handleSlideImageMouseDown" @touchstart="handleSlideImageMouseDown" />
        <div v-if="!isPass" class="verify-refresh" @click="refresh">
          <SvgIcon icon="ep:refresh-right" size="20px" />
        </div>
        <div class="loading" :class="loading ? 'is-loading-now' : ''">
          <SvgIcon icon="ep:loading" class="is-loading" />
          <span>{{ t('common', 'loadingText') }}</span>
        </div>
        <transition name="tips">
          <span v-if="text" :class="isPass ? 'success-bg' : 'error-bg'" class="verify-tips">
            {{ text }}
          </span>
        </transition>
      </div>
    </div>
    <!-- 公共部分 -->
    <div ref="VerifySlideControlRef" class="verify-slide-control" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
      <div class="verify-tips">{{ t('captcha', 'slide') }}</div>
      <div class="verify-slide-indicator" :class="[isMouseDown ? 'is-mousedown' : '', isPass === undefined ? '' : isPass ? 'is-success' : 'is-error']" :style="{ width: slideIndicatorWidth + 'px' }" />
      <div
        ref="verifySlideButtonRef"
        class="verify-slide-button"
        :class="[isMouseDown ? 'is-mousedown' : '', isPass === undefined ? '' : isPass ? 'is-success' : 'is-error']"
        :style="{ transform: 'translateX(' + slideButtonLeft + 'px)' }"
        @mousedown="handleSlideButtonMouseDown"
        @touchstart="handleSlideButtonMouseDown">
        <SvgIcon :icon="isPass === undefined ? 'ci:arrow-right' : isPass ? 'ep:check' : 'ep:close'" />
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
.verify-box {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;

  &-default {
    --el-verify-size: var(--el-component-size);

    .verify-slide-button {
      .el-icon {
        font-size: 20px !important;
      }
    }
  }

  &-large {
    --el-verify-size: var(--el-component-size-large);

    .verify-slide-button {
      .el-icon {
        font-size: 24px !important;
      }
    }
  }

  &-small {
    --el-verify-size: var(--el-component-size-small);

    .verify-slide-button {
      .el-icon {
        font-size: 16px !important;
      }
    }
  }
}

.verify-image-wrap {
  position: relative;
  z-index: 1;
  width: 100%;
  padding-bottom: 16px;
  overflow: hidden;
  pointer-events: auto;
  border-radius: 2px;
  user-select: none;

  &.is-hover-mode {
    position: absolute;
    bottom: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity .3s linear, bottom .3s linear;

    &.is-visible{
      bottom: var(--el-verify-size);
      opacity: 1;
      visibility: visible;
    }
  }
}

.verify-image-panel {
  position: relative;
  z-index: 1;
  width: 100%;
  padding-top: 50%;
  overflow: hidden;
  pointer-events: auto;
  border-radius: 2px;
  user-select: none;

  .verify-bg-image {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-repeat: no-repeat;
    background-size: 100%;
    border-radius: 2px;
    user-select: none;
    perspective: 1000;
    -webkit-user-drag: none;
  }

  .verify-slide-image {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    width: auto;
    height: 100%;
    pointer-events: auto;
    cursor: pointer;
    background-repeat: no-repeat;
    background-size: 100%;
    backface-visibility: hidden;
    user-select: none;
    perspective: 1000;
    -webkit-user-drag: none;
  }

  .verify-refresh {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 4;
    display: flex;
    width: 28px;
    height: 28px;
    color: var(--el-color-white);
    text-align: center;
    pointer-events: auto;
    cursor: pointer;
    background: rgba(0, 0, 0, 0.2);
    align-items: center;
    justify-content: center;
  }

  .verify-tips {
    position: absolute;
    bottom: 0;
    left: 0;
    z-index: 3;
    display: flex;
    width: 100%;
    height: 32px;
    line-height: 32px;
    color: var(--el-color-white);
    user-select: none;
    align-items: center;
    justify-content: center;
  }

  .success-bg {
    background-color: rgba(var(--el-color-success-rgb), 0.5);
  }

  .error-bg {
    background-color: rgba(var(--el-color-error-rgb), 0.5);
  }

  .tips-enter,
  .tips-leave-to {
    bottom: -30px;
  }

  .tips-enter-active,
  .tips-leave-active {
    transition: bottom 0.5s;
  }

  .loading {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 10;
    display: flex;
    width: 100%;
    height: 100%;
    background: var(--el-fill-color);
    opacity: 0;
    visibility: hidden;
    transition: opacity .1s linear;
    align-items: center;
    justify-content: center;
    flex-direction: column;

    &.is-loading-now {
      opacity: 1;
      visibility: visible;
    }

    span {
      margin-top: 10px;
      font-size: 14px;
    }
  }
}

.verify-slide-control {
  position: relative;
  z-index: 2;
  height: var(--el-verify-size);
  background-color: var(--el-fill-color-light);
  border: 1px solid var(--el-border-color);
  border-radius: 2px;

  .verify-slide-indicator {
    position: absolute;
    top: -1px;
    left: -1px;
    z-index: 4;
    width: var(--el-verify-size);
    height: var(--el-verify-size);
    border: 1px solid transparent;
    border-radius: 2px;

    &.is-mousedown {
      background-color: var(--el-color-primary-light-9);
      border: 1px solid var(--el-color-primary);
    }

    &.is-success {
      background-color: var(--el-color-success-light-9);
      border: 1px solid var(--el-color-success);
      transition: width 0.2s linear;
    }

    &.is-error {
      background-color: var(--el-color-error-light-9);
      border: 1px solid var(--el-color-error);
      transition: width 0.2s linear;
    }
  }

  .verify-slide-button {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 10;
    display: flex;
    width: var(--el-verify-size);
    height: 100%;
    cursor: pointer;
    background-color: var(--el-bg-color);
    border-radius: 2px;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
    transition: background .2s linear;
    align-items: center;
    justify-content: center;
    user-select: none;
    perspective: 1000;
    -webkit-user-drag: none;

    &.is-mousedown,
    &:hover {
      color: var(--el-color-white);
      background-color: var(--el-color-primary);
    }

    &.is-success {
      color: var(--el-color-white);
      background-color: var(--el-color-success);
      transition: transform 0.2s linear;
    }

    &.is-error {
      color: var(--el-color-white);
      background-color: var(--el-color-error);
      transition: transform 0.2s linear;
    }
  }

  .verify-tips {
    position: absolute;
    bottom: 0;
    left: 0;
    z-index: 1;
    display: flex;
    width: 100%;
    height: 100%;
    padding-left: var(--el-verify-size);
    color: var(--el-text-color-primary);
    align-items: center;
    justify-content: center;
    user-select: none;
  }
}
</style>
